home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / What's New? / Sample Code / Java / JavaRadioStation / Source / com / apple / jens / mp3 / MP3File.java < prev   
Encoding:
Java Source  |  2000-09-28  |  19.9 KB  |  688 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        MP3File.java
  3.     
  4.     Copyright:     © Copyright 1999-2000 Apple Computer, Inc. All rights reserved.
  5.     
  6.     Disclaimer:    IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
  7.                 ("Apple") in consideration of your agreement to the following terms, and your
  8.                 use, installation, modification or redistribution of this Apple software
  9.                 constitutes acceptance of these terms.  If you do not agree with these terms,
  10.                 please do not use, install, modify or redistribute this Apple software.
  11.  
  12.                 In consideration of your agreement to abide by the following terms, and subject
  13.                 to these terms, Apple grants you a personal, non-exclusive license, under Apple’s
  14.                 copyrights in this original Apple software (the "Apple Software"), to use,
  15.                 reproduce, modify and redistribute the Apple Software, with or without
  16.                 modifications, in source and/or binary forms; provided that if you redistribute
  17.                 the Apple Software in its entirety and without modifications, you must retain
  18.                 this notice and the following text and disclaimers in all such redistributions of
  19.                 the Apple Software.  Neither the name, trademarks, service marks or logos of
  20.                 Apple Computer, Inc. may be used to endorse or promote products derived from the
  21.                 Apple Software without specific prior written permission from Apple.  Except as
  22.                 expressly stated in this notice, no other rights or licenses, express or implied,
  23.                 are granted by Apple herein, including but not limited to any patent rights that
  24.                 may be infringed by your derivative works or by other works in which the Apple
  25.                 Software may be incorporated.
  26.  
  27.                 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
  28.                 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
  29.                 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  30.                 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
  31.                 COMBINATION WITH YOUR PRODUCTS.
  32.  
  33.                 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
  34.                 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  35.                 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  36.                 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
  37.                 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
  38.                 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
  39.                 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  40.                 
  41.     Change History (most recent first):
  42.  
  43. */
  44.  
  45.  
  46. package com.apple.jens.mp3;
  47.  
  48. import java.io.*;
  49. import java.util.Hashtable;
  50.  
  51. import com.apple.mrj.MRJFileUtils;
  52. import com.apple.mrj.MRJOSType;
  53.  
  54.  
  55. /** Provides read-only access to MP3 metadata,
  56.     including MPEG data such as bitrate and length,
  57.     and ID3 tags such as track title and artist.
  58.     Supports MPEG level 1 and 2, layers 1-3;
  59.     and ID3, ID3v2.2 and ID3v2.3.
  60.     
  61. */
  62.     
  63. public class MP3File extends File {
  64.  
  65.     public MP3File( String path ) {
  66.         super(path);
  67.     }
  68.     
  69.     
  70.     public MP3File( File file ) {
  71.         super(file.getPath());
  72.     }
  73.     
  74.     
  75.     public MP3File( File dir, String name ) {
  76.         super(dir,name);
  77.     }
  78.     
  79.     
  80.     // FILE TYPE CHECKING:
  81.     
  82.     
  83.     /** Checks the filename suffix (and filetype, if on Mac OS)
  84.         to determine whether the given file appears to be an MP3 file.
  85.         It does <i>not</i> look at the contents of the file. */
  86.     public static boolean isMP3FileType( File file ) {
  87.         if( file.isDirectory() || !file.exists() )
  88.             return false;
  89.         if( file.getName().toLowerCase().endsWith(".mp3") )
  90.             return true;
  91.         try{
  92.             MRJOSType type = MRJFileUtils.getFileType(file);
  93.             return type == kTypeMPG3 || type == kTypeMPEG;
  94.         }catch( IOException x ) {
  95.             x.printStackTrace(System.err);
  96.             return false;
  97.         }
  98.     }
  99.     
  100.     /** Checks the filename suffix (and filetype, if on Mac OS)
  101.         to determine whether this file appears to be an MP3 file.
  102.         It does <i>not</i> look at the contents of the file. */
  103.     public boolean isMP3FileType( ) {
  104.         return isMP3FileType(this);
  105.     }
  106.     
  107.     private static final MRJOSType kTypeMPG3  = new MRJOSType("MPG3");
  108.     private static final MRJOSType kTypeMPEG = new MRJOSType("MPEG");
  109.     
  110.     
  111.     // MPEG ACCESSORS:
  112.     
  113.     
  114.     /** Returns the <i>approximate</i> length in seconds of the audio.
  115.         This is computed based on the bitrate and the length of the entire file,
  116.         so it can be significantly off if there is a lot of data in the file
  117.         other than actual MPEG frames (e.g. lengthy ID3 tags.)
  118.         It is also blissfully ignorant of variable-bit-rate files! */
  119.     public long getLength( ) throws IOException, MP3Exception {
  120.         readData();
  121.         return fLength;
  122.     }
  123.     
  124.     
  125.     /** Returns the MPEG level -- MP3 is level 1. */
  126.     public int getMPEGLevel( ) throws IOException, MP3Exception {
  127.         readData();
  128.         return fMPEGLevel;
  129.     }
  130.     
  131.     
  132.     /** Returns the MPEG layer -- MP3 is layer 3. */
  133.     public int getMPEGLayer( ) throws IOException, MP3Exception {
  134.         readData();
  135.         return fMPEGLayer;
  136.     }
  137.     
  138.     
  139.     /** Returns the bitrate (bits per second) of the audio data.
  140.         This will of course not be valid for variable-bit-rate files. */
  141.     public int getBitRate( ) throws IOException, MP3Exception {
  142.         readData();
  143.         return fBitRate;
  144.     }
  145.     
  146.     
  147.     /** Returns the sample rate (samples per second) of the data. */
  148.     public int getSampleRate( ) throws IOException, MP3Exception {
  149.         readData();
  150.         return fSampleRate;
  151.     }
  152.     
  153.     
  154.     // ID3 ACCESSORS:
  155.     
  156.     
  157.     /** Returns the version of the ID3 tag, as one of the constants
  158.         kNoID3, kID3v1, kID3v22, kID3v23 */
  159.     public int getID3Version( ) throws IOException, MP3Exception {
  160.         readData();
  161.         return fID3Version;
  162.     }
  163.     
  164.     
  165.     private Tag findTag( String name ) throws IOException, MP3Exception {
  166.         readData();
  167.         return (Tag) fTags.get(name);
  168.     }
  169.     
  170.     
  171.     /** Returns true if the file's ID3 data contains the given named tag.
  172.         Tag names are four-character strings defined in the ID3 specification. */
  173.     private boolean hasTag( String name ) throws IOException, MP3Exception {
  174.         readData();
  175.         return fTags.containsKey(name);
  176.     }
  177.     
  178.     
  179.     /** Returns the raw contents of a named ID3 tag as a byte array.
  180.         Tag names are four-character strings defined in the ID3 specification. */
  181.     public byte[] getRawTag( String name ) throws IOException, MP3Exception {
  182.         Tag tag = findTag(name);
  183.         if( tag != null )
  184.             return tag.raw;
  185.         else
  186.             return null;
  187.     }
  188.     
  189.     
  190.     /** Returns the contents of a textual ID3 tag as a Java String.
  191.         Tag names are four-character strings defined in the ID3 specification. */
  192.     public synchronized String getTag( String name ) throws IOException, MP3Exception {
  193.         Tag tag = findTag(name);
  194.         if( tag == null )
  195.             return null;
  196.         if( tag.text == null ) {
  197.             // Convert tag data to text:
  198.             String encoding;
  199.             int start=0, length=tag.raw.length;
  200.             if( fID3Version==kID3v1 ) {
  201.                 encoding = "Cp437";
  202.             } else {
  203.                 // In ID3v2, 1st byte indicates encoding to use
  204.                 encoding = (tag.raw[0]==0) ?"ISO-8859-1" :"Unicode";
  205.                 start++;
  206.                 length--;
  207.             }
  208.             try{
  209.                 tag.text = new String(tag.raw,start,length, encoding);
  210.             }catch( UnsupportedEncodingException x ) {
  211.                 if(DEBUG)System.err.println("MP3File: Unsupported encoding "+encoding);
  212.                 tag.text = new String(tag.raw);
  213.             }
  214.         }
  215.         return tag.text;
  216.     }
  217.     
  218.     
  219.     /** Returns the flags associated with a named ID3 tag.
  220.         Note that only ID3v2.3 defines flags; if the file has an older-format ID3 tag,
  221.         zero will be returned.
  222.         Tag names are four-character strings defined in the ID3 specification. */
  223.     public int getTagFlags( String name ) throws IOException, MP3Exception {
  224.         Tag tag = findTag(name);
  225.         if( tag != null )
  226.             return tag.flags;
  227.         else
  228.             return 0;
  229.     }
  230.     
  231.     
  232.     /** Returns the title of the audio track, as given in the ID3 tag. */
  233.     public String getTitle( ) throws IOException, MP3Exception {
  234.         return getTag(kTagTitle);
  235.     }
  236.     
  237.     /** Returns the name of the artist who recorded the audio track, as given in the ID3 tag. */
  238.     public String getArtist( ) throws IOException, MP3Exception {
  239.         return getTag(kTagArtist);
  240.     }
  241.     
  242.     /** Returns the name of the album on which the audio track appears, as given in the ID3 tag. */
  243.     public String getAlbum( ) throws IOException, MP3Exception {
  244.         return getTag(kTagAlbum);
  245.     }
  246.     
  247.     /** Returns the year the audio track was recorded, as given in the ID3 tag. */
  248.     public String getYear( ) throws IOException, MP3Exception {
  249.         return getTag(kTagYear);
  250.     }
  251.     
  252.     /** Returns the comment field from the ID3 tag.
  253.         In ID3v1, this field is often used to give a URL,
  254.         but later versions have specific fields for various types of URLs. */
  255.     public String getComment( ) throws IOException, MP3Exception {
  256.         return getTag(kTagComment);
  257.     }
  258.     
  259.     
  260.     // PUBLIC CONSTANTS:
  261.     
  262.     
  263.     /** ID3 format version codes */
  264.     public static final int
  265.         kNoID3  = 0,
  266.         kID3v1  = 1,
  267.         kID3v22 = 2,
  268.         kID3v23 = 3;
  269.     
  270.     
  271.     /** IDs of some of the more common frames.
  272.         Use these constants with <code>getTag</code>, etc. */
  273.     public static final String
  274.         kTagTitle        = "TIT2",
  275.         kTagArtist        = "TPE1",
  276.         kTagAlbum        = "TALB",
  277.         kTagYear        = "TYER",
  278.         kTagComment        = "COMM";
  279.     
  280.     
  281.     //.MPEG INTERNALS:
  282.     
  283.     
  284.     private void readMP3Info( RandomAccessFile io ) throws IOException, MP3Exception {
  285.         // First 'synchronize' to the first MPEG frame:
  286.         io.seek(0);
  287.         int header;
  288.         try{
  289.             while(true) {
  290.                 header = io.readByte() & 0xFF;
  291.                 if( header == 0xFF ) {
  292.                     header = io.readByte() & 0xFF;
  293.                     if( header >= 0xE0 )
  294.                         break;
  295.                 }
  296.             }
  297.         }catch( EOFException x ) {
  298.             throw new MP3Exception("No MPEG frames found -- is this an MP3 file?");
  299.         }
  300.         
  301.         // Read the next 2 bytes of the header:
  302.         header = (header<<16) | (io.readShort() & 0xFFFF);
  303.         
  304.         if(DEBUG)System.out.println("MP3File: MPEG frame header = "+
  305.                                         Integer.toHexString(header));
  306.         
  307.         // Now parse the 3-byte frame header:
  308.         fMPEGLevel = bit(header,19) ?1 :2;
  309.         fMPEGLayer = readMPEGLayer(header);
  310.         //fProtection = bit(header,16) == 0;
  311.         fBitRate = readBitRate(header);
  312.         fSampleRate = readSampleRate(header);
  313.         fLength = computeLength(io);
  314.         
  315.         if(DEBUG)System.out.println("MP3File: level="+fMPEGLevel
  316.                                     +", layer="+fMPEGLayer
  317.                                     +", bitrate="+fBitRate
  318.                                     +", sampleRate="+fSampleRate
  319.                                     +", length="+fLength);
  320.     }
  321.     
  322.     
  323.     private static boolean bit( int i, int bit ) {
  324.         return ((i>>bit) & 1) != 0;
  325.     }
  326.     
  327.     private static int readMPEGLayer( int header ) {
  328.         header = (header >> 17) & 0x03;
  329.         if( header==0 )
  330.             return 0;
  331.         else
  332.             return 4-header;
  333.     }
  334.     
  335.     private int readBitRate( int header ) {
  336.         int value = (header >> 12) & 0x0F;
  337.         if( value == 15 )
  338.             return 0;    // illegal
  339.         else
  340.             return kBitRateTable[ 15*(3*(fMPEGLevel-1) + (fMPEGLayer-1))
  341.                                   + value ] * 1000;
  342.         // (Yes, "k" means "1000" here, not "1024" ... if I use 1024, the MP3 players
  343.         // that connect to the server start getting left behind.
  344.     }
  345.     
  346.     private static final int[] kBitRateTable =
  347.         {  0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,    // MPEG1 layer1
  348.            0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,    // MPEG1 layer2
  349.            0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,    // MPEG1 layer3 [MP3]
  350.            0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,    // MPEG2 layer1
  351.            0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,    // MPEG2 layer2
  352.            0,  8, 16, 24, 32, 64, 80, 56, 64,128,160,112,128,256,320 };    // MPEG2 layer3
  353.     
  354.     private int readSampleRate( int header ) {
  355.         int rate;
  356.         switch( (header>>10) & 0x03 ) {
  357.             case 0: rate=44100; break;
  358.             case 1: rate=48000; break;
  359.             case 2: rate=32000; break;
  360.             default: return 0; // illegal
  361.         }
  362.         if( fMPEGLevel > 1 )
  363.             rate >>= 1;
  364.         return rate;
  365.     }
  366.     
  367.     private long computeLength( RandomAccessFile io ) throws IOException {
  368.         long dataSize = io.length() - (io.getFilePointer()-3);
  369.         return dataSize * 8 / fBitRate;
  370.     }
  371.     
  372.     
  373.     // ID3 INTERNALS:
  374.     
  375.     
  376.     private synchronized void readData( ) throws IOException, MP3Exception {
  377.         if( fRead )
  378.             return;
  379.             
  380.         RandomAccessFile io = new RandomAccessFile(this,"r");
  381.         try{
  382.             fTags = new Hashtable();
  383.             
  384.             if( ! readID3v2(io) &&
  385.                 ! readID3v1(io) ) {
  386.                     if(DEBUG)System.err.println("MP3File: No ID3 tag found");
  387.                     fID3Version = kNoID3;
  388.             }
  389.             
  390.             readMP3Info(io);
  391.             
  392.             fRead = true;
  393.             
  394.         }finally{
  395.             try{
  396.                 io.close();
  397.             }catch( IOException x ) {
  398.             }
  399.         }
  400.     }
  401.     
  402.     
  403.     /** Attempts to read ID3v1 data; returns false if there isn't any */
  404.     private boolean readID3v1( RandomAccessFile io ) throws IOException, ID3Exception {
  405.         // Read the last 128 bytes, where the ID3v1 tag lives:
  406.         long start = io.length() - 128;
  407.         if( start <= 0 )
  408.             return false;
  409.         byte[] buf = new byte[128];
  410.         io.seek(start);
  411.         io.readFully(buf);
  412.         
  413.         // Check for the ID3v1 header:
  414.         if( buf[0]!='T' || buf[1]!='A' || buf[2]!='G' ) {
  415.             if(DEBUG)System.out.println("MP3File: No ID3v1 tag found: "
  416.                                         +(char)buf[0]+(char)buf[1]+(char)buf[2]);
  417.             return false;
  418.         }
  419.         
  420.         if(DEBUG)System.out.println("MP3File: Found ID3v1 tag...");
  421.         
  422.         // Now tweeze out each tag value:
  423.         int pos = 3;
  424.         for( int i=0; i<kID3v1TagCount; i++ ) {
  425.             int size = kID3v1TagSize[i];
  426.             int len;
  427.             for( len=size; len>0; len-- ) {
  428.                 char c = (char)buf[pos+len-1];
  429.                 if( c!=' ' && c!='\0' )
  430.                     break;
  431.             }
  432.             if( len > 0 ) {
  433.                 byte[] raw = new byte[len];
  434.                 System.arraycopy(buf,pos, raw,0, len);
  435.                 Tag tag = new Tag();
  436.                 tag.id = kID3v1TagName[i];
  437.                 tag.raw = raw;
  438.                 tag.flags = 0;
  439.                 fTags.put(tag.id,tag);
  440.             }
  441.             pos += size;
  442.         }
  443.         
  444.         fID3Version = kID3v1;
  445.         return true;
  446.     }
  447.     
  448.     
  449.     /** Attempts to read ID3v2 data; returns false if there isn't any */
  450.     private boolean readID3v2( RandomAccessFile io ) throws IOException, ID3Exception {
  451.         io.seek(0);
  452.         
  453.         // Check for the ID3v2 header:
  454.         byte[] buf = new byte[3];
  455.         io.readFully(buf);
  456.         if( buf[0]!='I' || buf[1]!='D' || buf[2]!='3' )
  457.             return false;
  458.         
  459.         // Check the ID3v2 version:
  460.         int version = io.readShort() & 0xFFFF;
  461.         if( version <= 0x02FF )
  462.             fID3Version = kID3v22;
  463.         else if( version <= 0x03FF )
  464.             fID3Version = kID3v23;
  465.         else
  466.             throw new ID3Exception("ID3v2 version "+(version/256.0)+" is not supported");
  467.         
  468.         if(DEBUG)System.err.println("MP3File: ID3v2 version is "+Integer.toHexString(version));//TEMP
  469.         
  470.         // Read the flags:
  471.         int flags;
  472.         boolean unsync, extended, experimental;
  473.         flags = io.readByte() << 24;
  474.         if( fID3Version==kID3v22 ) {
  475.             unsync       = (flags & 0x80000000) != 0;
  476.             if( (flags & 0x40000000) != 0 )
  477.                 throw new ID3Exception("Cannot read compressed ID3v2.2 tag");
  478.             extended = false;
  479.         } else {
  480.             unsync       = (flags & 0x80000000) != 0;
  481.             extended     = (flags & 0x40000000) != 0;
  482.             //experimental = (flags & 0x20000000) != 0;
  483.         
  484.             if( (flags & 0x1FFFFFFF) != 0 ) {
  485.                 // Spec says that tag might not be readable if unknown flags are set
  486.                 throw new ID3Exception("ID3v2 unknown flags in use: 0x"+Integer.toHexString(flags));
  487.             }
  488.         }
  489.         
  490.         // Read the total header size:
  491.         int totalSize = read7BitInt(io.readInt());
  492.         
  493.         if(DEBUG)System.err.println("MP3File: totalSize="+totalSize);
  494.         
  495.         // Apply unsynchronization if necessary:
  496.         DataInput in;
  497.         if( unsync )
  498.             in = new DataInputStream( new UnsyncInputStream(io) );
  499.         else
  500.             in = io;
  501.         
  502.         // Read the extended header, if any:
  503.         if( extended ) {
  504.             int size = in.readInt();
  505.             if( size < 0 )
  506.                 throw new ID3Exception("Illegal extended header size "+size);
  507.             totalSize -= 4 + size;
  508.             // Just ignore extended header contents
  509.             in.skipBytes(size);
  510.         }
  511.         
  512.         // Read frames one at a time:
  513.         byte[] idBuf = new byte[ fID3Version==kID3v22 ?3 :4 ];
  514.         while( totalSize > 0 ) {
  515.             // Read the frame name:
  516.             in.readFully(idBuf);
  517.             if( idBuf[0]==0 ) {
  518.                 // We've reached the end of tags and gone into blank padding space.
  519.                 totalSize = 0;    // skip totalSize test after end of loop
  520.                 break;
  521.             }
  522.             Tag tag = new Tag();
  523.             tag.id = new String(idBuf,"ISO-8859-1");
  524.             if(DEBUG)System.err.println("MP3File: read tag '"+tag.id+"'");
  525.             if( fID3Version==kID3v22 )
  526.                 tag.id = mapID3v22Name(tag.id);
  527.             
  528.             // Read the data size:
  529.             int size;
  530.             if( fID3Version==kID3v22 ) {
  531.                 // ID3v2.2 field size is 3(!) bytes:
  532.                 size = ((in.readShort() & 0xFF)<<8) | (in.readByte() & 0xFF);
  533.                 totalSize -= 6;
  534.             } else {
  535.                 size = in.readInt();
  536.                 tag.flags =  in.readShort() & 0xFFFF;
  537.                 totalSize -= 10;
  538.             }
  539.             if( size < 1 || size > totalSize )
  540.                 throw new ID3Exception("Illegal frame size "+size+" for frame '"+tag.id+"'");
  541.             totalSize -= size;
  542.             
  543.             // Read the raw data:
  544.             tag.raw = new byte[size];
  545.             in.readFully(tag.raw);
  546.             
  547.             fTags.put(tag.id,tag);
  548.         }
  549.         
  550.         if( totalSize != 0 )
  551.             throw new ID3Exception("ID3 size mismatch");
  552.         
  553.         return true;
  554.     }
  555.     
  556.     
  557.     /** Converts an integer from the 7-bit-byte encoding used in ID3v2,
  558.         in which the high bit of each byte is unused. */
  559.     private int read7BitInt( int i ) {
  560.         return ((i & 0x0000007F))
  561.              | ((i & 0x00007F00) >> 1)
  562.              | ((i & 0x007F0000) >> 2)
  563.              | ((i & 0x7F000000) >> 3);
  564.     }
  565.     
  566.     
  567.     /** Maps an ID3v2.2 frame name to the equivalent ID3v2.3 name. */
  568.     private String mapID3v22Name( String name ) {
  569.         for( int i=0; i<kID3v22Converter.length; i+=2 )
  570.             if( kID3v22Converter[i].equals(name) )
  571.                 return kID3v22Converter[i+1];
  572.         return name;    // by default return it unchanged
  573.     }
  574.     
  575.         
  576.     // PRIVATE DATA:
  577.     
  578.     
  579.     /** True if the data has been read yet. */
  580.     private boolean fRead;
  581.     
  582.     private int fMPEGLevel;
  583.     private int fMPEGLayer;
  584.     private int fBitRate;
  585.     private int fSampleRate;
  586.     
  587.     /** Audio length in seconds. */
  588.     private long fLength;
  589.     /** Version of the ID3 tag. */
  590.     private int fID3Version;
  591.     
  592.     /** Hashtable mapping ID3v2.3 tag names to Tag objects */
  593.     private Hashtable fTags;
  594.     
  595.     
  596.     // PRIVATE CONSTANTS:
  597.     
  598.     
  599.     private static final int kID3v1TagCount = 7;
  600.     private static final int[] kID3v1TagSize = {30, 30, 30, 4, 29, 1, 1};
  601.     private static final String[] kID3v1TagName = {    "TIT2", "TPE1", "TALB", "TYER",
  602.                                                     "COMM", "TRCK", "TCON"};
  603.     
  604.     /** Maps ID3v2.2 tag names to their ID3v2.3 equivalents */
  605.     private static final String[] kID3v22Converter = {    "TAL","TALB",    "TBP","TBPM",
  606.                                                         "TCM","TCOM",    "TCR","TCOP",
  607.                                                         "TDA","TDAT",    "TDY","TDLY",
  608.                                                         "TEN","TENC",    "TXT","TEXT",
  609.                                                         "TFT","TFLT",    "TIM","TIME",
  610.                                                         "TT1","TIT1",    "TT2","TIT2",
  611.                                                         "TT3","TIT3",    "TKE","TKEY",
  612.                                                         "TLA","TLAN",    "TLE","TLEN",
  613.                                                         "TMT","TMED",    "TOT","TOAL",
  614.                                                         "TOF","TOFN",    "TOL","TOLY",
  615.                                                         "TOA","TOPE",    "TOR","TORY",
  616.                                                         "TP1","TPE1",    "TP2","TPE2",
  617.                                                         "TP3","TPE3",    "TP4","TPE4",
  618.                                                         "TPA","TPOS",    "TPB","TPUB",
  619.                                                         "TRD","TRDA",    "TSI","TSIZ",
  620.                                                         "TRC","TSRC",    "TYE","TYER" };
  621.     
  622.     
  623.     // INNER CLASSES:
  624.     
  625.     
  626.     /** A simple passive struct to hold tag data. */
  627.     private class Tag {
  628.         public String    id;
  629.         public int        flags;
  630.         public byte[]    raw;
  631.         public String    text;
  632.     }
  633.     
  634.     
  635.     /** An InputStream that performs the "unsyncing" process that decodes ID3v2 tag data */
  636.     private class UnsyncInputStream extends InputStream {
  637.     
  638.         public UnsyncInputStream( RandomAccessFile in ) {
  639.             fIn = in;
  640.         }
  641.         
  642.         public int read( ) throws IOException {
  643.             int b = fIn.read();
  644.             if( fHadFF ) {
  645.                 fHadFF = false;
  646.                 if( b == 0x00 )
  647.                     b = fIn.read();        // Skip 00 byte after FF [ID3 2.3 spec sect. 5]
  648.             } else if( b==0xFF )
  649.                 fHadFF = true;
  650.             return b;
  651.         }
  652.         
  653.         private final RandomAccessFile fIn;
  654.         private boolean fHadFF = false;
  655.         
  656.     }
  657.     
  658.     
  659.     // DEBUGGING STUFF:
  660.     
  661.     
  662.     /** For testing -- can be run directly from command line or JBindery. */
  663.     public static void main( String[] args ) throws IOException, MP3Exception {
  664.         File dir = new File(args[0]);        // 1st arg is directory of MP3 files
  665.         String[] list = dir.list();
  666.         for( int i=0; i<list.length; i++ ) {
  667.             MP3File f = new MP3File(dir,list[i]);
  668.             System.out.println("***** Testing file "+f);
  669.             if( f.isMP3FileType() ) {
  670.                 System.out.println("\tlevel  =  "+f.getMPEGLevel());
  671.                 System.out.println("\tlayer  =  "+f.getMPEGLayer());
  672.                 System.out.println("\tbitrate=  "+f.getBitRate());
  673.                 System.out.println("\tsmprate=  "+f.getSampleRate());
  674.                 System.out.println("\tlength =  "+f.getLength()+" secs");
  675.                 System.out.println("\tID3    =  "+f.getID3Version());
  676.                 System.out.println("\ttitle  = '"+f.getTitle()+"'");
  677.                 System.out.println("\tartist = '"+f.getArtist()+"'");
  678.                 System.out.println("\talbum  = '"+f.getAlbum()+"'");
  679.                 System.out.println("\tyear   = '"+f.getYear()+"'");
  680.                 System.out.println("\tcomment= '"+f.getComment()+"'");
  681.             }
  682.         }
  683.     }
  684.     
  685.     private static final boolean DEBUG = false;
  686.     
  687. }
  688.